package com.netease.nimlib.fts.index;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.netease.nimlib.fts.query.FTSQuery;
import com.netease.nimlib.search.model.NIMIndexException;
import com.netease.nimlib.search.model.NIMIndexRecord;

public final class NIMIndex {
	private static final String LIB = "nimindex";

	private static boolean loaded;

	static {
		try {
			System.loadLibrary(LIB);
			loaded = true;
		} catch (UnsatisfiedLinkError e) {
			e.printStackTrace();
		}

		if (loaded) {
			nativeInit();
		}
	}

	public static final class Query {
		private long statement;

		private NIMIndexRecord tmp;

		private Query(long statement) {
			this.statement = statement;
		}

		public final boolean next() throws NIMIndexException {
			tmp = new NIMIndexRecord();
			return nativeQueryNext(statement, tmp);
		}

		public final NIMIndexRecord get() {
			return tmp;
		}

		public final void close() {
			nativeQueryClose(statement);
		}
	}

	private static final class QueryN {
		private final long statement;

		private NIMIndexRecord[] tmp;

		private QueryN(long statement, int n) {
			this.statement = statement;
			tmp = new NIMIndexRecord[n];
		}

		public final boolean next() throws NIMIndexException {
			// reset
			for (int i = 0; i < tmp.length; i++) {
				tmp[i] = null;
			}

			return nativeQueryN(statement, tmp);
		}

		public final NIMIndexRecord[] get() {
			return tmp;
		}

		public final void close() {
			nativeQueryClose(statement);
		}
	}

	private final String path;

	private final boolean writeMode;

	// native pointer
	private long conn;

	public NIMIndex(String path, boolean writeMode) {
		this.path = path;
		this.writeMode = writeMode;
	}

	public final void open() throws NIMIndexException {
		conn = nativeOpen(path, writeMode);
	}

	public final void close() {
		nativeClose(conn);
	}

	public final void transBegin() throws NIMIndexException {
		nativeTransOp(conn, 0);
	}

	public final void transEnd(boolean commit) throws NIMIndexException {
		nativeTransOp(conn, commit ? 1 : 2);
	}

	public final long getVersion(long type) throws NIMIndexException {
		return nativeGetVersion(conn, type);
	}

	public final void setVersion(long type, long ver) throws NIMIndexException {
		nativeSetVersion(conn, type, ver);
	}

	public final Query query(long type, String query, int limit, String id)
			throws NIMIndexException {
		long statement = nativeQueryOpen(conn, type,
				FTSQuery.queryString(query), limit, id);
		if (statement != 0) {
			return new Query(statement);
		}

		return null;
	}

	public final List<NIMIndexRecord> queryN(long type, String query, int limit, String id) throws NIMIndexException {
		long statement = nativeQueryOpen(conn, type, FTSQuery.queryString(query), limit, id);
		List<NIMIndexRecord> results = new ArrayList<>();
		if (statement != 0) {
			QueryN q = new QueryN(statement, 20);
			while (q.next()) {
				NIMIndexRecord[] records = q.get();
				for (NIMIndexRecord r : records) {
					if (r != null) {
						results.add(r);
					} else {
						break;
					}
				}
			}

			// add a self
			NIMIndexRecord[] records = q.get();
			for (NIMIndexRecord r : records) {
				if (r != null) {
					results.add(r);
				} else {
					break;
				}
			}

			q.close();
		}

		return results;
	}

	public final void insert(NIMIndexRecord record) throws NIMIndexException {
		nativeInsert(conn, record);
	}

	public final void insert(NIMIndexRecord[] records) throws NIMIndexException {
		nativeBulkInsert(conn, records);
	}

	public final void insert(Collection<NIMIndexRecord> records)
			throws NIMIndexException {
		NIMIndexRecord[] aRecords = new NIMIndexRecord[records.size()];
		records.toArray(aRecords);
		insert(aRecords);
	}

	public final void delete(long type, long dataid) throws NIMIndexException {
		nativeDelete(conn, type, dataid, null);
	}

	public final void delete(long type, String id) throws NIMIndexException {
		nativeDelete(conn, type, -1, id);
	}

	public final void delete(long type) throws NIMIndexException {
		nativeDelete(conn, type, -1, null);
	}

	public static final void test() throws NIMIndexException {
		nativeTest();
	}

	public static final void testToken(String query) throws NIMIndexException {
		nativeTestToken(query);
	}

	//
	// native interface
	//

	private static native void nativeInit();

	private static native long nativeOpen(String path, boolean writeMode);

	private static native void nativeClose(long conn);

	private static native void nativeTransOp(long conn, int op);

	private static native long nativeGetVersion(long conn, long type);

	private static native void nativeSetVersion(long conn, long type, long ver);

	private static native long nativeQueryOpen(long conn, long type,
			String query, int limit, String id);

	private static native void nativeQueryClose(long query);

	private static native boolean nativeQueryN(long query,
			NIMIndexRecord[] outRecords);

	private static native boolean nativeQueryNext(long query,
			NIMIndexRecord record);

	private static native void nativeInsert(long conn, NIMIndexRecord record);

	private static native void nativeBulkInsert(long conn,
			NIMIndexRecord[] records);

	private static native void nativeDelete(long conn, long type, long dataid,
			String id);

	//
	// native test interface
	//

	private static native void nativeTest();

	private static native void nativeTestToken(String query);
}